热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

产物|品级_Android编译系统概览

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android编译系统-概览相关的知识,希望对你有一定的参考价值。http://duanqz.github.io/201

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Android编译系统-概览相关的知识,希望对你有一定的参考价值。


http://duanqz.github.io/2015-08-08-build-system-part1-overview#Android%E5%90%AF%E6%99%BA%E8%A7%82

 

目录


  • 1. 概要
  • 2. 背景
    • 2.1 Makefile规则的基本形式
    • 2.2 Resurvise Make的缺陷
    • 2.3 Android编译系统的设计意图
  • 3. 设计原理
    • 3.1 编译系统的初始化
      • 3.1.1 产品级(Product)的参数配置
      • 3.1.2 平台级(Board)的参数配置
      • 3.1.3 芯片级(Architecture)的参数配置
      • 3.1.4 模块级(Module)的参数配置
    • 3.2 编译系统的运行过程
      • 3.2.1 汇集Makefile零散片段
      • 3.2.2 生成Makefile目标依赖
      • 3.2.3 编译产出
    • 3.3 编译SDK

请尊重原创版权,转载注明出处。


1. 概要

编译,就是将高级语言转换成机器语言。譬如,通过gcc将C语言编译成可以运行的二进制;通过javac将Java语言编译成可以在Java虚拟机上可以运行的字节码。

对于简单的项目,源文件数量较少,通常只需要几条命令,组织一下源文件,调用一下编译器,生成一个可以运行的文件,就算是一个“编译系统”; 但对于大型的项目,文件数量很多,通常会被组织成众多的模块,模块之间构成依赖关系,这就不是简单几条命令就能够成为“编译系统”了。一个大型项目的编译系统,需要管理好各个模块的依赖关系,组织好大量的编译中间产物,具备随时应对模块变更的扩展性,同时,也能够高效的完成编译任务。

在Linux上,一些传统大型项目的编译系统都是基于make这个工具,make并不是编译器,仍然需要调用gcc或javac等编译命令来对源码进行编译,make的输入是一个Makefile文本文件,make只是按照Makefile文件中定义的规则来完成工作。所以,这些编译系统中最核心的就是Makefile定义的编译规则和编译顺序,包括:哪些源文件需要编译、如何编译、哪些文件存在对其他文件的依赖,优先编译哪些文件。同其他编程语言一样,Makefile也有自己的语法,当正确书写完Makefile文件后,通过一个make命令,就能自动完成大型项目的编译。

android编译系统也是基于make的,要编译出整个Android系统的镜像文件并打包成刷机包(OTA Package),编译出SDK和文档(JavaDoc),同时,Android引入了很多第三方开源项目,需要兼顾不同模块的编译,仅仅是这些就已经够让Android编译系统受苦了,还别提支持不同设备厂商的硬件类型,方便设备厂商进行定制这些兼容性、扩展性、编译效率的问题。Android编译系统的复杂度可见一斑,纵观整个编译,除了大量的编译规则文件(Makefile文件片段),还有很多Shell和Python脚本组织在一起,基于make,但又不同于已有传统项目的编译系统,Android有自己的一套编译机制。

本文会分析以下问题:



Android编译系统的设计背景是什么? 这涉及到Android编译系统设计的意图。
Android编译系统的设计原理是什么? 这涉及到Android编译系统运转的内在机制。



2. 背景

2.1 Makefile规则的基本形式

Makefile文件的书写规则,不是本文要讨论的内容,但是Makefile规则的基本形式还是有必要在这里点出来,作为后续分析的理论基础。

# 规则语法 # 示例
TARGET... : PREREQUISITES... main.o: main.c main.h
COMMAND 1 gcc -c main.c
... ...
COMMAND N echo "Compile finished"

  • TARGET: 表示当前规则要达成的目标,通常为最终生成的文件,或者为了最终生产的文件而产生的中间文件。譬如示例中的 main.o 就是一个中间文件
  • PREREQUISITES: 表示当前规则所依赖的前提条件,只有前提条件满足了,才能够生成目标。这些前提条件通常为另外的规则所定义的目标,这就决定了编译顺序,只有优先编译完所依赖的目标,才能编译当前规则所定义的目标。譬如示例中所依赖的的 main.cmain.h 两个文件都必须存在,才能开始编译 main.o
  • COMMAND: 表示当前规则的执行命令。一个规则下,可以有多条命令。当所依赖的条件满足后,就会依次执行这些命令序列,譬如调用gcc、文件拷贝、输出日志等

复杂的Makefile规则,都是由若干基本的规则组合而成。最终,Makefile会被解析成一个有向无环图(Directed Acyclic Graph, DAG), 每一个目标(Target)构成DAG的节点,每一个依赖关系(Dependency)构成DAG的边。


2.2 Resurvise Make的缺陷

同所有基于make的编译系统一样,Android也需要Makefile文件来定义编译规则和编译顺序。但与大多数编译系统不同的是,Android并不是Recursive Make的,那么什么是Resursive Make呢?



当在Makefile文件中,使用make命令来编译另一个模块时,就构成了Resurvise Make
对于一个大型项目而言,可以为每个子系统构建一个Makefile文件,然后在最顶级的Makefile文件中,调用make命令来编译各个子系统:
   subsystem:
     cd subdir && make


Resurvise Make的设计能够降低编译的复杂度,更容易理解和维护,很多Linux上的大型项目,包括Linux内核的编译系统,都是采用的Resurvise Make设计,但Android并没有采用,因为这种设计存在很多缺陷。早在1997年,Peter Miller就在Recursive Make Considered Harmful这篇论文中,指出了Resurvise Make会导致编译系统“做得太少(do too little)”或“做得太多(do too much)”, “do too little”会导致最终编译产物可能是错误的结果,而“do too much”会导致编译效率下降。

TODO:补充Resurvise Make缺陷的示例


2.3 Android编译系统的设计意图

由于Resurvise Make的缺陷,Android采用了Non-Resursive Make的方式,将所有的编译规则集中于一个Makefile中,可以想象最终成型的这个Makefile是极其庞大的。 为了提升编译效率和灵活性,需要对模块的编译进行控制:单个模块可以单独进行编译,不需要的模块不会被重新编译,以便节省编译时间。

在上述意图的驱使下,Android编译系统有以下主要的要求 (更详细的要求,请查阅build/core/build-system.html ):


  • 编译出多种目标: 除了最终Android系统的产物(譬如:system.img, boot.img),编译系统还要能够编译出很多实用的工具(譬如:aapt, adb),这些工具不仅是编译环境需要的,也是开发者需要的。

  • 支持多平台: Android需要在Linux和Mac上进行编译,编译产出也需要支持Linux和Mac。编译系统对Windows的支持并不好,但面向开发者的SDK是支持Windows的。

  • Makefile片段化:最终的规则集中于一个Makefile,并不意味着编译系统会维护这么大一个Makefile。为了提高代码的重复利用率,编译系统包含很多Makefile片段,最终通过include将片段包含进来。 每一个待编译的模块都会有一个Android.mk,其内容也是Makefile片段。

  • 自动构建依赖: 模块之间依赖关系是自动构建的,这意味着,我们只需要使用编译系统提供的接口,定义一个模块的编译规则,不需要关心编译系统如何管理众多模块之间的依赖关系。

至此,我们分析了Android编译系统重新设计的背景,主要是为了避免Resursive Make存在的缺陷,同时应对多模块多平台的高效率编译需求。


3. 设计原理

在Android源码的根目录有一个Makefile文件,有效内容只有一行:

include build/core/main.mk

所有的编译规则都定义在build/core/main.mk文件中,最终所有的Makefile片段都将汇聚在这一个文件中。

Android编译系统如此强大,要变成Makefile的最终形态,当然是要经过很长一段路的,下图是整个编译系统的框架:

我们会基于这个图来分析整个Android编译系统的设计原理:


  • 从Android源码来看,编译系统的核心功能位于build/core/目录, 在device和vendor目录下,存放了与具体机型相关的配置,这些配置信息都是.mk文件的形式存放的(譬如BoardConfig.mkAndroidProducts.mk), 另外,每一个模块的编译配置信息都是以独立的Android.mk文件的形式分散在各个模块的子目录中。

  • 编译系统需要经过初始化(setup)来完成必要的参数赋值。初始化的操作命令很简单,但实际要配置的参数是非常多的,Android支持不同平台上不同产品,甚至是不同模块的编译, 也支持编译SDK以及PC上一些常用的工具,编译系统通过配置信息的指导,来完成具体的编译任务。

  • 每一次的编译任务,就是给make一个Makefile文件,所以,每次编译任务,编译系统就会经过汇集众多零散Makefile片段的过程。编译整个工程和编译一个模块,最终 汇集成的Makefile是不一样的,这样就能做到灵活对各个模块进行编译。

  • Android将编译产物都放到了out/目录下,out/目录下又有host/target/两个子目录,分别表示PC上和手机上的编译产物。

接下来,我们就来进一步分析编译系统的设计细节。


3.1 编译系统的初始化

要使Android编译系统运转起来,首先需要经过初始化,其实就是完成所有参数的配置。Android编译系统的配置,可以分为四个层级,从下到上依次是:结构级(Architecture),芯片级(Board),产品级(Product),模块级(Module)


  • 芯片级(Architecture):涵盖CPU体系结构和指令集的配置,譬如arm, x86, mips

  • 平台级(Board):这个层级的配置通常定义在BoardConfig.mk,包括内核、驱动、CPU等与硬件紧密相关的

  • 产品级(Product): 这个层级的配置通常定义在AndroidProducts.mk,包括产品名称、需要包含哪些模块和文件等

  • 模块级(Module): 这个层级的配置都是由Android.mk定义的,模块具体的一些配置,包括模块名称、模块类型、对其他模块的依赖等。 要知道Android一共有多少个模块,可以在AOSP的源码根目录下执行以下命令:

$ find . -name Android.mk | wc -l # 笔者在Android 5.0.1下执行这个命令,得到的结果是3868,汗!

这几个层级的配置并非独立的,而是在不同参数配置下相互影响的。Android提供两种方式来进行参数配置:运行envsetup.sh或配置buildspec.mk


  • 运行envsetup.sh

    Android提供了一个环境初始化的脚本build/envsetup.sh, 通过source命令,便可以将该脚本添加到shell环境中。接着,便发现多了一个lunch命令,我们就是通过这个命令来配置Android初始化的参数。 除了lunch,还会有很多其他命令,譬如: m, mm, mmm,我们会在编译系统(2)-初始化过程这篇文章中来详细介绍envsetup.sh的工作过程。

$ source build/envsetup.sh # 将envsetup.sh添加到shell执行环境中
$ lunch # 通过lunch来交互式的完成参数配置

  • 配置buildspec.mk

    该文件需要置于Android源码的根目录,Android提供一个配置模板build/buildspec.mk.default, 只需要将拷贝到根目录,重命名后,根据需要修改文件内容便可完成参数的配置。

    :支持这种文件配置的方式来完成初始化,是考虑到有些固定的编译场景,不需要每次都重复运行envsetup.sh脚本来配置相同的参数。


3.1.1 产品级(Product)的参数配置

无论是采用哪种方式,都会涉及到以下几个重要的参数:


  • TARGET_PRODUCT:目标产品。这个参数的取值来自于一个具体产品的定义,通常位于device/[manufacture]/[product]下的AndroidProducts.mk文件中, 通过PRODUCT_MAKEFILES这个属性来汇集所有产品级别的配置,包括产品名称PRODUCT_NAME,产品品牌PRODUCT_BRAND等。产品名称PRODUCT_NAME实际上就对应到了TARGET_PRODUCT

    Android 5.0.1提供了一些默认的目标产品:aosp_arm、aosp_arm64、aosp_mips、aosp_mips64、aosp_x86、aosp_x86_64,分别表示arm, mips, x86上32位和64位的产品类型。 到Android实际支持的机型,就有aosp_hammerhead, aosp_manta,分别表示LGE Nexus 5和SAMSUNG 4S。

  • TARGET_BUILD_VARIANT:目标产品的版本。每一个模块都可以通过LOCAL_MODULE_TAGS这个参数来标记自己,可选的标记值有user, debug, eng, tests, optional, 或samples。 设定目标产品的类型,就能筛选出指定标记的模块,只将符合要求的模块编译打包到最终的产品中去。

    TARGET_BUILD_VARIANT有以下取值,除了筛选模块,还有一些调试级别的差异:


    • eng:对应到工程版。编译打包所有模块。同时ro.secure=0, ro.debuggable=1, ro.kernel.android.checkjni=1,表示adbd处于ROOT状态,所有调试开关打开
    • userdebug:对应到用户调试版。同时ro.debuggable=1,打开调试开关,但并没有放开ROOT权限
    • user: 对应到用户版。同时ro.secure=1,ro.debuggable=0,关闭调试开关,关闭ROOT权限。最终发布到用户手上的版本,通常都是user
  • TARGET_BUILD_TYPE: 目标产品的类型。只有release和debug两种取值,默认的取值为release。Android源码中包含一些调试专用的代码,当取值为debug时,这些调试代码会编译到最终的产品中去。

  • TARGET_TOOLS_PREFIX: 编译工具链的路径前缀。默认情况下,Android使用prebuilts目录下的工具,但通过这个值也可自行定制为其他目录。

TARGET_PRODUCT设定后,编译系统就可以基于它获取其他参数了。这个产品是哪个体系结构,哪个芯片,内核的命令参数是什么? 这些硬件相关的参数都是通过BoardConfig.mk来配置的,编译系统会搜寻device和vendor目录下的$(TARGET_DEVICE)/BoardConfig.mk,这样就能找到对应产品的的芯片级(Board)配置了。


3.1.2 平台级(Board)的参数配置

BoardConfig.mk文件中参数主要有以下几个类别:


  • CPU体系结构: TARGET_CPU_ABI, TARGET_CPU_VARIANT, TARGET_ARCH, TARGET_ARCH_VARIANT等。其中一些参数的取值不同,会连带引发其他参数的不同。

  • 内核参数: BOARD_KERNEL_BASE, BOARD_KERNEL_PAGESIZE, BOARD_KERNEL_CMDLINE这些参数最终会被打包到boot分区的镜像文件中(boot.img),作为内核的启动参数。

  • 分区镜像: TARGET_USERIMAGES_USE_EXT4, BOARD_BOOTIMAGE_PARTITION_SIZE, BOARD_SYSTEMIMAGE_PARTITION_SIZE等与分区格式、分区大小相关的参数。

  • 驱动: BOARD_HAVE_BLUETOOTH, BOARD_WLAN_DEVICE等与蓝牙、wifi硬件配置相关的参数


3.1.3 芯片级(Architecture)的参数配置

不同的产品(Product)配置会对应到不同的平台(Board)配置,而平台(Board)的配置也会影响到芯片(Architecture)的配置。BoardConfig.mk中定义的TARGET_ARCHTARGET_ARCH_VARIANT两个参数决定了TARGET_ARCH_SPECIFIC_MAKEFILE这个芯片级(Architecture)的配置文件,它的值等于build/core/combo/arch/$(TARGET_ARCH)/$(TARGET_ARCH_VARIANT).mk

Android默认定义了arm, arm64, mips, mips64, x86, x86_64这几组与CPU芯片相关的编译参数。


3.1.4 模块级(Module)的参数配置

Android编译系统的设计理念是将模块级别的配置独立出来,每个模块的Android.mk都是独立的。在编译单个模块时,会将模块的Android.mk添加到main.mk中,形成一个Makefile。 当然,模块的Android.mk必须遵循编译系统定义的规则,具体的配置细节可以参见编译系统(4)-定制。这里只说明编译系统提供给模块编译的接口:


接口变量接口定义作用
BUILD_EXECUTABLEexecutable.mk编译二进制可执行文件,如adbd
BUILD_HOST_EXECUTABLEhost_executable.mk编译PC上的二进制可执行文件,如aapt
BUILD_JAVA_LIBRARYjava_library.mk编译动态Java库文件,如framework.jar
BUILD_HOST_JAVA_LIBRARYhost_java_library.mk编译PC上的Java库文件,如signapk.jar
BUILD_SHARED_LIBRARYshared_library.mk编译动态的库文件,如libfilterfw.so
BUILD_STATIC_LIBRARYstatic_library.mk编译静态的库文件,如libip6tc.so
BUILD_PACKAGEpackage.mk编译APK,如SystemUI.apk

所有接口定义的源文件,都在build/core目录下,在Android.mk中,只需要引用这些变量,就能触发一个模块的编译,不同的模块使用不用的编译方式。 在将一个Android.mk文件includemain.mk中的时候,也会依次将上述变量定义的.mk文件include进来,从而生成最终的Makefile配置。


3.2 编译系统的运行过程

编译系统的运行过程可以分为两部分:


  • 合成最终的Makefile:零散的Makefile片段,会按照引用关系汇集到main.mk中,作为最终编译的Makefile

  • 根据依赖关系逐步构建出最终产物:Makefile的编译规则最终成型为一个DAG,make会按照“后根顺序(post-order)”来遍历DAG,被依赖的目标总是先被执行


3.2.1 汇集Makefile零散片段

通过Makefileinclude语法,就能将其他Makefile片段包含到当前Makefile文件中来,当我们在Android目录下执行make命令时,实际上输入文件是根目录下的Makefile,所有的片段最终都汇集到该文件中。

大体的汇集过程如下图所示:

最终Makefile文件中仅仅引入了main.mk, main.mk位于build/core目录,望文生意,这就表示已经进入Android编译系统最为核心的部分了,main.mk会做编译环境检查,定义最重要的编译目标(droid),依次引入其他功能片段:


  • help.mk,最优先引入的片段,文件内容很简单,就是定义了一个名为help的目标,通过输入make help命令,就可以看到该目标的输出结果是一些最主要的make目标的帮助信息。

  • config.mk,文件内容很庞大,目的就是为了配置编译环境。该文件定义了用于其他模块编译的常量(BUILD_JAVA_LIBRARY, CLEAR_VARS等),也定义了编译时所依赖工具的本地路径(aapt, minigzip等),同时也会引入与基于机型配置相关的其他片段(BoardConfig.mk, AndroidProducts.mk等)。

  • cleanbuild.mk,定义了installclean这个编译目标,不同于clean,执行make installclean的时候,并不会完整的删除out/目录,而是仅仅删除与当前TARGET_PRODUCT, TARGET_BUILD_VARIANT, PRODUCT_LOCALES这属性关联到的编译产出。通俗一点来说,就是删除out/target/product/目录下本次机型编译的产物,out/host目录下的文件是保留下来的。

  • definitions.mk,定义了大量的函数,这些函数都是编译系统的其他文件将用到的。譬如:my-dir, all-subdir-makefiles, find-subdir-files等,通过Makefile$(call func_name)就能调用这些函数。

  • dex_preopt.mk,为了提升代码的运行效率,Android会对可执行文件做优化,即将dex格式的文件转换为odex格式的文件。在ART虚拟机下,仍然采用dex(Dalvik Executable)odex(Optimized Dalvik Executable)这个文件的命名方式,但实际上ART与Dalvik的文件格式是不同的。

  • Android.mk,Android有全编译和模块编译之分:


    • 全编译,会通过build/tools/findleaves.py这个脚本将所有模块的Android.mk加载到一个名为 subdir_makefiles这个变量中,然后逐个引入$subdir_makefiles中的Makefile片段;
    • 模块编译,是通过命令解析将待编译模块的Android.mk文件加载到ONE_SHOT_MAKEFILE这个变量中,编译时,仅仅是引入ONE_SHOT_MAKEFILE中的Makefile片段。

    Android.mk的编写模板基本都是一致的,它会引入很多编译系统已经初始化好的变量,譬如CLEAR_VARS, BUILD_JAVA_LIBRARY, 其实就是引入变量所对应的.mk文件,所以Android.mk的生成过程,也是一个Makefile片段的汇集过程。

  • post_clean.mk,在引入待编译模块后,就可以定义模块的清除规则了。该片段定义了基于上一次编译产出的清除规则,譬如,某个模块的AIDL文件或者资源发生了变化,那再次编译这个模块时,就需要清除上一次的编译产物。

  • legacy_prebuilts.mk,定义了GRANDFATHERED_ALL_PREBUILT变量,表示不需要经过编译的预装文件,譬如gps.conf(GPS配置文件), radio.img(射频分区镜像文件),这些文件都是预编译好的,只需要拷贝到编译产出即可。Android定义了一个默认的PREBUILT列表,而且不希望第三方改动这个列表。当第三方有预编译文件,但又不在PREBUILT列表中时,就需要通过PRODUCT_COPY_FILES这个变量来指定了。

  • Makefile,不同于AOSP根目录下的Makefile,这个Makefile位于build/core目录下,Android官方对这个文件的解释是”定义一些杂乱的编译规则(miscellaneous rules)”,实际上,这个文件相当重要,诸如system.img, recovery.img, userdata.img, cache.img的目标定义都在这个文件中,更笼统点说,out/target/product/PRODUCT_NAME/目录下大部分的编译产出都是由该文件定义的。


3.2.2 生成Makefile目标依赖

最终Makefile所定义的依赖关系可以用一个有向无环图(Directed Acyclic Graph, DAG)来表示,如果解析Makefile规则,发现存在一个依赖环,那就不是合法的Makefile规则。

可以把编译系统构建的依赖关系分成系统级和模块级别两个层面:


  • 系统级的依赖关系,是指最终编译出Android系统所涉及到的目标之间的依赖。譬如编译出system.img有哪些依赖目标需要先编译完成。这一层的依赖是由编译系统定义的。

  • 模块级的依赖关系,是指各个模块之间的依赖。譬如模块A依赖模块B,那么就需要将这个依赖关系告诉编译系统,只有当模块B编译完成了,才能开始编译模块A。这一层的依赖是由各个模块单独定义的。

3.2.2.1 系统级的依赖关系

最终生成的DAG是极其庞大的,为了说明问题,我们仅列出了一些关键的编译目标之间的依赖关系图:


  • droid,是最顶层的编译目标,执行make命令时,就会默认编译这个目标,但这个目标并没有什么实质内容,仅仅作为所依赖目标的组合。其中dist_files是编译产物emma_meta.zip,主要用作测试覆盖率; apps_only这个编译目标是由TARGET_BUILD_APPS的值决定的,当只编译指定的app时,就会编译这个目标,否则就编译droidcore这个目标。

  • droidcore,Android全编译的顶层目标,也仅仅作为所依赖目标的组合。boot.img, recovery.img, userdata.img, cache.img, system.img这些最终刷入设备的镜像都是被依赖的目标。

  • files,该目标囊括所有预编译的模块prebuilt,需要编译安装的模块modules_to_install,这些模块的编译产物输出到最终需要打包的分区镜像文件中。

  • system.img,在Makefile中,真正的编译目标是systemiamge,它依赖于INSTALLED_SYSTEMIMAGE这个变量,其实就是编译完成后out/目录下的system.img, 要编译system.img,当然依赖于很多模块的编译产出(由ALL_DEFAULT_INSTALLED_MODULES变量描述的),这里就构成了两个目标依赖于同一个目标的依赖关系,但仍然没有构成环。

  • userdata.img, cache.img, recovery.img,这些都是分区镜像文件,最终刷入设备上对应的分区,它们都是被droidcore依赖的目标,当然,这些目标的生成也依赖于很多其他的目标。

  • installed-files.txt, 一个文本文件,最终生成在out/目录下,文件内容列出了所有安装的模块。

这里并没有把更多的依赖细节体现出来,只是展开了几个很顶层的目标,实际上,各个目标之间的依赖关系是错综复杂的。

3.2.2.2 模块级的依赖关系

每一个模块的Android.mk文件中,都可以通过LOCAL_REQUIRED_MODULES的值,来设置所依赖的其他模块,但这只是一个依赖关系的定义,对LOCAL_REQUIRED_MODULES的处理还是编译系统完成的。

Android.mk中,都会调用编译系统提供的模板,来编译一个特定的模块,这是通过引用Makefile变量来实现的,譬如:$(BUILD_HOST_EXECUTABLE)表示编译PC机上的可执行程序,$(BUILD_PACKAGE)表示编译一个APK, $(BUILD_JAVA_LIBRARY)表示编译一个Java库文件。

每一个$(BUILD_XX)的变量,都会对应到一个.mk文件,而这些.mk文件也会引用其他.mk文件,最终都会落到引用base_rules.mk中:

base_rules.mk中,待编译的模块会把自己添加到ALL_MODULES这个变量中,如此一来,当所有的Android.mk引入完成后,ALL_MODULES就记录了所有待编译的模块。 所有被依赖的模块会保存在ALL_MODULES.$(m).REQUIRED这个变量中,$(m)就是当前被引入的模块,在编译$(m)这个模块的时候,就会顺着ALL_MODULES.$(m).REQUIRED变量找到被依赖的模块,从而构建出依赖关系图。


3.2.3 编译产出

我们最关心的当然是一次编译能够有什么产出,所有的编译产物都在out/目录下,有两个子目录:host/表示针对PC上的产物,target/表示针对移动设备上的产物。所有编译产物的生成过程可以大致分为三步:


  1. 各个模块编译出中间产物。这些中间产物都位于host/target/下面的obj/子目录中,对于C代码而言,中间产物通常就是.o文件;对于Java代码而言,中间产物通常就是.jar文件。

    target/下面的obj/为例,有以下类别的中间产物:


    • EXECUTABLES: BUILD_EXECUTABLE的中间产物,譬如adbd, app_process等模块
    • JAVA_LIBRARIES: BUILD_LIBRARY的中间产物,譬如framework, services等模块
    • SHARED_LIBRARIES: BUILD_SHARED_LIBRARY的中间产物, 譬如libart, libc, libsqlite等模块
    • STATIC_LIBRARIES: BUILD_STATIC_LIBRARY的中间产物,譬如libapplypatch等模块
    • APPS: BUILD_PACKAGE的中间产物, 譬如Browser, Email, Settings
    • ETC: 譬如init.rc, mac_permissions.xml, *.ttf字体文件等模块
    • PACKAGING: 打包时的中间文件,譬如systemimg, userdataimg, target_files, apkcerts, 这些编译目标需要打包其他编译产物
    • lib: 编译完成的so库,最终会按照目录结构加载到system/lib目录。

    对于host下面的obj/,也有类似的目录结构。

  2. 链接各个中间产物,生成可执行的文件。对于EXECUTABLES,会在模块的临时目录生成一个二进制程序;对于SHARED_LIBRARIES,会在模块的临时目录生成一个LINKED的子目录,存放这最终编译完成的so库文件。

  3. 打包二进制文件,生成镜像文件。譬如,将各种apk、jar和so文件拷贝到system/目录,打包生成system.img镜像。


3.3 编译SDK

TODO


推荐阅读
  • Ceph API微服务实现RBD块设备的高效创建与安全删除
    本文旨在实现Ceph块存储中RBD块设备的高效创建与安全删除功能。开发环境为CentOS 7,使用 IntelliJ IDEA 进行开发。首先介绍了 librbd 的基本概念及其在 Ceph 中的作用,随后详细描述了项目 Gradle 配置的优化过程,确保了开发环境的稳定性和兼容性。通过这一系列步骤,我们成功实现了 RBD 块设备的快速创建与安全删除,提升了系统的整体性能和可靠性。 ... [详细]
  • 从无到有,构建个人专属的操作系统解决方案
    操作系统(OS)被誉为程序员的三大浪漫之一,常被比喻为计算机的灵魂、大脑、内核和基石,其重要性不言而喻。本文将详细介绍如何从零开始构建个人专属的操作系统解决方案,涵盖从需求分析到系统设计、开发与测试的全过程,帮助读者深入理解操作系统的本质与实现方法。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • 本文通过具体的代码示例,详细解析了如何在Java中利用AspectJ实现面向切面编程(AOP)。AspectJ是Java平台上的一个AOP框架,通过引入连接点(JoinPoint)、通知(Advice)和切面(Aspect)等核心概念,扩展了Java语言的功能。文章首先介绍了AspectJ的基本概念,然后通过实际编码示例展示了如何定义和应用切面,帮助读者更好地理解和掌握这一技术。 ... [详细]
  • 深入解析JavaScript词法分析的具体流程与常见问题 ... [详细]
  • 本文深入探讨了 iOS 开发中 `int`、`NSInteger`、`NSUInteger` 和 `NSNumber` 的应用与区别。首先,我们将详细介绍 `NSNumber` 类型,该类用于封装基本数据类型,如整数、浮点数等,使其能够在 Objective-C 的集合类中使用。通过分析这些类型的特性和应用场景,帮助开发者更好地理解和选择合适的数据类型,提高代码的健壮性和可维护性。苹果官方文档提供了更多详细信息,可供进一步参考。 ... [详细]
  • 解决基于XML配置的MyBatis在Spring整合中出现“无效绑定语句(未找到):com.music.dao.MusicDao.findAll”问题的方法
    在将Spring与MyBatis进行整合时,作者遇到了“无效绑定语句(未找到):com.music.dao.MusicDao.findAll”的问题。该问题主要出现在使用XML文件配置DAO层的情况下,而注解方式配置则未出现类似问题。作者详细分析了两个配置文件之间的差异,并最终找到了解决方案。本文将详细介绍问题的原因及解决方法,帮助读者避免类似问题的发生。 ... [详细]
  • 《软件测试精要》深度解析与实战经验分享
    《软件测试精要》深度解析与实战经验分享,系统梳理了软件测试的核心概念与关键原则,结合实际项目中的测试经验和教训,详细探讨了测试分类、测试权衡要素、测试效率、测试覆盖率以及测试框架的引入和用例设计等内容,为读者提供了全面而实用的指导。 ... [详细]
  • 本文将详细介绍在Android应用中添加自定义返回按钮的方法,帮助开发者更好地理解和实现这一功能。通过具体的代码示例和步骤说明,本文旨在为初学者提供清晰的指导,确保他们在开发过程中能够顺利集成返回按钮,提升用户体验。 ... [详细]
  • 如何将PHP文件上传至服务器及正确配置服务器地址 ... [详细]
  • HBase在金融大数据迁移中的应用与挑战
    随着最后一台设备的下线,标志着超过10PB的HBase数据迁移项目顺利完成。目前,新的集群已在新机房稳定运行超过两个月,监控数据显示,新集群的查询响应时间显著降低,系统稳定性大幅提升。此外,数据消费的波动也变得更加平滑,整体性能得到了显著优化。 ... [详细]
  • Python学习:环境配置与安装指南
    Python作为一种跨平台的编程语言,适用于Windows、Linux和macOS等多种操作系统。为了确保本地已成功安装Python,用户可以通过终端或命令行界面输入`python`或`python3`命令进行验证。此外,建议使用虚拟环境管理工具如`venv`或`conda`,以便更好地隔离不同项目依赖,提高开发效率。 ... [详细]
  • 在Linux环境下编译安装Heartbeat时,常遇到依赖库缺失的问题。为确保顺利安装,建议预先通过yum安装必要的开发库,如glib2-devel、libtool-ltdl-devel、net-snmp-devel、bzip2-devel和ncurses-devel等。这些库是编译过程中不可或缺的组件,能够有效避免编译错误,确保Heartbeat的稳定运行。 ... [详细]
  • 【前端开发】深入探讨 RequireJS 与性能优化策略
    随着前端技术的迅速发展,RequireJS虽然不再像以往那样吸引关注,但其在模块化加载方面的优势仍然值得深入探讨。本文将详细介绍RequireJS的基本概念及其作为模块加载工具的核心功能,并重点分析其性能优化策略,帮助开发者更好地理解和应用这一工具,提升前端项目的加载速度和整体性能。 ... [详细]
  • 当前,众多初创企业对全栈工程师的需求日益增长,但市场中却存在大量所谓的“伪全栈工程师”,尤其是那些仅掌握了Node.js技能的前端开发人员。本文旨在深入探讨全栈工程师在现代技术生态中的真实角色与价值,澄清对这一角色的误解,并强调真正的全栈工程师应具备全面的技术栈和综合解决问题的能力。 ... [详细]
author-avatar
韭花帖_420
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有